//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
using OpenLS.Chart;
using OpenLS.Core;
using OpenLS.Core.Notification;
using OpenLS.Core.Serialization;
using OpenLS.Drawing;
using OpenLS.Spreadsheet.Chart;
using OpenLS.Spreadsheet.Data;
using OpenLS.Spreadsheet.Drawing;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Extensibility;
using OpenLS.Spreadsheet.Internals;
using OpenLS.Spreadsheet.Parsing;
using Expression=OpenLS.Spreadsheet.Expressions.Expression;

namespace OpenLS.Spreadsheet
{
    /// <summary>
    /// Represents a spreadsheet workbook. A workbook is a container of sheets.
    /// </summary>
    public partial class Workbook : ViewModelBase, IOfficeXmlSerializable, 
        IThemedElement, IReferenceFactory, IWorkbook, IFormatValueConverter
    {
        internal IEnumerable<Cell> GetCells()
        {
            return Enumerable.SelectMany(Enumerable.OfType<Worksheet>(this.Sheets), w => w.GetCells());
        }
#if DEBUG
        public bool IsValid
        {
            get
            {
                return Enumerable.All(Enumerable.OfType<Worksheet>(Sheets), ws => ws.IsValid);
            }
        }
        public bool CheckFormattingRecords()
        {
            List<StyleFormattingRecord> allStyleRecords =  new List<StyleFormattingRecord>(Enumerable.Select(
                Enumerable.Union(
                this.Styles, this.BuiltInStyles), s => s.Record));
            foreach (var r in Enumerable.Where(Enumerable.Select(this.GetCells(), c => c.FormattingRecord), r => r != null && r.Parent != null))
            {
                Contract.Assert(allStyleRecords.Contains(r.Parent));
            }
            return true;
        }
#endif

       

        private object _tag;
        /// <summary>
        /// Gets or sets an arbitrary object value that can be used to store custom information about this workbook.
        /// </summary>
        public object Tag
        {
            get { return _tag; }
            set { _tag = value; notifyPropertyChanged("Tag");}
        }
        
        internal RowColumnGeneration RowColumnGeneration = new RowColumnGeneration(0);
        /// <summary>
        /// Occurs when the user clicks on a cell with an external link
        /// </summary>
        public event EventHandler<LinkNavigateEventArgs> CellLinkNavigate;

        internal void InvokeCellLinkNavigate(LinkNavigateEventArgs e)
        {
            EventHandler<LinkNavigateEventArgs> handler = CellLinkNavigate;
            if (handler != null) handler(this, e);
        }

        private const string sharedStringRelationship =
            @"http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";

        private const string stylesRelationship =
            @"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";


        internal CalculationManager CalculationManager = new CalculationManager();
        private const string xmlName = "workbook";
        private static readonly A1NamingConvention A1Nc = new A1NamingConvention();
        private readonly SpreadsheetDocument _document;
        private readonly List<ExternalLink> _externalLinks = new List<ExternalLink>();
        private readonly List<FileRecoveryProperties> _fileRecoveryPr = new List<FileRecoveryProperties>();
        private readonly UndoManager _permanentUndoManager = new UndoManager();
        private ObservableCollection<Sheet> _sheets ;
        private readonly List<WebPublishObject> _webPublishObjects = new List<WebPublishObject>();
        private CalculationProperties _calcPr;
        private List<DataConnection> _connections = new List<DataConnection>();
        private List<CustomWorkbookView> _customWorkbookViews;
        private FileVersion _fileVersion;
        private double? _maximumDigitWidthCache;
        private string _name = "Workbook"; //\\ loc?
        private NameManager _nameManager;
        private TableNameManager _tableNameManager;
        private CellNamingConvention _namingConvention = CellNamingConventions.A1NamingConvention;
        private UndoManager _previewUndoManager;
        private WorkbookWeakReference _selfWeakReference;
        private List<SheetInfo> _sheetInfos;
        private SmartTagProperties _smartTagPr;
        private List<SmartTagType> _smartTagTypes = new List<SmartTagType>();
        private StyleSheet _styleSheet;
        private Theme _theme;
        private WorkbookView _view;
        private WebPublishing _webPublishing;
        private WorkbookProperties _workbookPr;
        private WorkbookProtection _workbookProtection;

        internal Workbook(SpreadsheetDocument document)
        {
            Contract.Requires(document != null);
#if DEBUG
            //\\MyListener.Init();;
#endif
            if (document == null)
                throw new ArgumentNullException("document");
            _styleSheet = new StyleSheet(this);
            _document = document;
            View = new WorkbookView(this);
            object dummy = Theme;
            
        }

        public IEnumerable<CellStyle> BuiltInStyles
        {
            get { return _styleSheet.BuiltInStylesNew; }
        }
        /// <summary>
        /// Gets all the cell styles in the workbook.
        /// </summary>
        public IEnumerable<CellStyle> Styles
        {
            get
            {
                foreach (CellStyle info in _styleSheet.Styles)
                {
                    yield return info;
                }
            }
        }

        internal NameManager NameManager
        {
            get
            {
                if (_nameManager == null)
                    _nameManager = new NameManager(this);
                return _nameManager;
            }
        }

        internal TableNameManager TableNameManager
        {
            get
            {
                if (_tableNameManager == null)
                    _tableNameManager = new TableNameManager(this);
                return _tableNameManager;
            }
        }

        internal ColorScheme ColorScheme
        {
            get
            {
                Theme th = Theme;
                if (th == null)
                    return null;
                return th.ColorScheme;
            }
        }

        internal virtual string InternalPrefix
        {
            get { return ""; }
        }

        internal double MaximumDigitWidth
        {
            get
            {
                if (_maximumDigitWidthCache == null)
                {
                    double result = 0;

                    Typeface tf;//\\ = new Typeface(FontFamily, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
                    tf = SpreadsheetFont.CreateTypeface(FontFamily, FontStyles.Normal, FontWeights.Normal);
                    for (int i = 0; i <= 9; i++)
                    {
                        var t =
                            new FormattedText(i.ToString(), CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
                                              tf, 11*4/3, Brushes.Black);
                        result = Math.Max(result, t.Width);
                    }
                    result = Math.Truncate(result);
                    _maximumDigitWidthCache = result;
                }
                return _maximumDigitWidthCache.Value;
            }
        }

        internal bool InPreviewMode
        {
            get { return _previewUndoManager != null; }
            set
            {
                if (InPreviewMode == value)
                    return;
                if (value)
                {
                    if (_previewUndoManager == null)
                        _previewUndoManager = new UndoManager();
                    if (Theme != null)
                        Theme.UndoManager = UndoManager;
                    return;
                }
                while (_previewUndoManager.UndoStack.Count > 0)
                    _previewUndoManager.Undo();
                _previewUndoManager = null;
                if (Theme != null)
                    Theme.UndoManager = UndoManager;
            }
        }
        /// <summary>
        /// Gets the undo manager
        /// </summary>
        public UndoManager UndoManager
        {
            get
            {
                Contract.Ensures(Contract.Result<UndoManager>() != null);
                if (_previewUndoManager != null)
                    return _previewUndoManager;
                return _permanentUndoManager;
            }
            //\\set { permanentUndoManager = value; }
        }


        /// <summary>
        /// Get or set whether undo is enabled
        /// </summary>
        public bool UndoEnabled
        {
            get { return UndoManager.IsEnabled; }
            set { UndoManager.IsEnabled = value; }
        }


        public FontFamily FontFamily
        {
            get
            {
                return Theme.FontScheme.MinorFont.Latin;
                //if (fontFamily == null)
            }
        }
        /// <summary>
        /// Get the spreadsheet document containing the current workbook
        /// </summary>
        public SpreadsheetDocument Document
        {
            get
            {
                Contract.Ensures(Contract.Result<SpreadsheetDocument>() != null);
                return _document;
            }
        }
        /// <summary>
        /// Get the naming convention. A1 is used by default
        /// </summary>
        internal CellNamingConvention NamingConvention
        {
            get { return _namingConvention; }
        }
        internal Sheet GetNextVisibleSheet (int direction)
        {
            int index = Sheets.IndexOf(View.ActiveSheet);
            index += direction;
            while (index >= 0 && index < Sheets.Count)
            {
                var result = this.Sheets[index];
                if (result.IsVisible)
                    return result;
                index += direction;
            }
            return View.ActiveSheet;

        }
        /// <summary>
        /// Get the collection of sheets in the workbook. In most cases, sheet are worksheets.
        /// </summary>
        public ObservableCollection<Sheet> Sheets
        {
            get
            {
                if (_sheets == null)
                {
                    _sheets = new ObservableCollection<Sheet>();
                    _sheets.CollectionChanged += (sender, e) =>
                                                     {
                                                         if (AreSheetsProtected)
                                                             throw new InvalidOperationException("Sheets are protected");
                                                     };
                }
                return _sheets;
            }
        }

        internal bool AreSheetsProtected
        {
            get { return this.Protection != null && Protection.LockStructure; }
        }
        /// <summary>
        /// Get or set the workbook view
        /// </summary>
        public WorkbookView View
        {
            get { return _view; }
            set { _view = value; }
        }


        private SharedStrings _sharedStrings = new SharedStrings();
        internal SharedStrings SharedStrings
        {
            get
            {
                Contract.Ensures(Contract.Result<SharedStrings>() != null);
                return _sharedStrings;
            }
            set
            {
                Contract.Requires(value != null);
                _sharedStrings = value;
            }
        }

        /// <summary>
        /// Get or set file sharing of the workbook
        /// </summary>
        public FileSharing FileSharing { get; set; }


        public StyleSheet StyleSheet
        {
            get { return _styleSheet; }
            set { _styleSheet = value; }
        }

        internal List<ExternalLink> ExternalLinks
        {
            get { return _externalLinks; }
        }
        /// <summary>
        /// Get or set the data connections
        /// </summary>
        public List<DataConnection> Connections
        {
            get { return _connections; }
            set { _connections = value; }
        }

        /// <summary>
        /// Get or set the name of the workbook
        /// </summary>
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
                notifyPropertyChanged("Name");
            }
        }

        public List<WebPublishObject> WebPublishObjects
        {
            get { return _webPublishObjects; }
        }


        public WebPublishing WebPublishing
        {
            get { return _webPublishing; }
            set { _webPublishing = value; }
        }

        public List<CustomWorkbookView> CustomWorkbookViews
        {
            get { return _customWorkbookViews; }
        }

        /// <summary>
        /// Get or set the workbook protection
        /// </summary>
        public WorkbookProtection Protection
        {
            get { return _workbookProtection; }
            set
            {
                _workbookProtection = value;
                notifyPropertyChanged("Protection");
            }
        }


        public List<SmartTagType> SmartTagTypes
        {
            get { return _smartTagTypes; }
            set { _smartTagTypes = value; }
        }

        public SmartTagProperties SmartTagProperties
        {
            get { return _smartTagPr; }
            set
            {
                _smartTagPr = value;
                notifyPropertyChanged("SmartTagProperties");
            }
        }


        public CalculationProperties CalculationProperties
        {
            get
            {
                if (_calcPr == null)
                {
                    _calcPr = new CalculationProperties();
                }
                return _calcPr;
            }
            set { _calcPr = value; }
        }

        /// <summary>
        /// Get the collection of defined names in the workbook
        /// </summary>
        public ObservableCollection<DefinedName> DefinedNames
        {
            get { return NameManager.definedNames; }
        }


        /// <summary>
        /// Get the collection of defined names in the workbook
        /// </summary>
        public ObservableCollection<TableDefinedName> TableDefinedNames
        {
            get { return TableNameManager.tableDefinedNames; }
        }

        /// <summary>
        /// Get or set the workbook properties
        /// </summary>
        public WorkbookProperties Properties
        {
            get { return _workbookPr; }
            set { _workbookPr = value; }
        }


        public FileVersion FileVersion
        {
            get { return _fileVersion; }
            set
            {
                _fileVersion = value;
                notifyPropertyChanged("FileVersion");
            }
        }

        public List<FileRecoveryProperties> FileRecoveryProperties
        {
            get { return _fileRecoveryPr; }
        }

        internal UndoUnitScope CurrentUndoScope { get; set; }

        internal bool CanUndo
        {
            get { return UndoManager.IsEnabled && UndoManager.UndoStack.Count > 0; }
        }

        internal bool CanRedo
        {
            get { return UndoManager.IsEnabled && UndoManager.RedoStack.Count > 0; }
        }

        public ExtensionElement Extension { get; set; }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region IReferenceFactory Members

        NumericReference IReferenceFactory.CreateNumericReference()
        {
            return new RangeNumericReference();
        }

        StringReference IReferenceFactory.CreateStringReference()
        {
            return new RangeStringReference();
        }

        MultiLevelStringReference IReferenceFactory.CreateMultiLevelStringReference()
        {
            return new RangeMultiLevelStringReference();
        }

        #endregion

        #region IThemedElement Members

        ///<summary>
        /// Get or set the theme of the workbook
        ///</summary>
        public Theme Theme
        {
            get
            {
                if (_theme == null)
                {
                    using (new UndoManagerDisabler(UndoManager))
                    {
                        Theme = BuiltInThemes.DefaultTheme;
                    }
                }
                return _theme;
            }
            set
            {
                using (CreateUndoScope(StringTable.Theme))
                {
                    if (UndoManager.IsEnabled)
                        UndoManager.Add(new SetThemeUndoUnit(this, _theme));

                    _maximumDigitWidthCache = null;
                    //\\ invalidate column width?
                    if (ThemeChanged != null)
                        ThemeChanged(this, new EventArgs());
                    if (_theme != null)
                    {
                        _selfWeakReference.Close();
                        //\\_theme.PropertyChanged -= EnsureWeakReference(_theme).value_PropertyChanged;
                        //\\ SelfWeakReference.UnregisterActions.Add(delegate { _theme.PropertyChanged -= SelfWeakReference.value_PropertyChanged; });
                    }
                    //\\_theme.PropertyChanged -= new PropertyChangedEventHandler(value_PropertyChanged);
                    _theme = value != null ? value.Clone() : null;
                    notifyPropertyChanged("Theme");
                    notifyPropertyChanged("FontFamily");
                    foreach (Worksheet ws in Sheets)
                    {
                        foreach (Column col in ws.Columns)
                            col.RaiseWidthChange();
                    }
                    if (_theme != null)
                    {
                        _theme.PropertyChanged +=
                            ensureWeakReference(_theme).ValuePropertyChanged;
                        _theme.UndoManager = UndoManager;
                    }
                }
            }
        }

        internal bool RecalcOnNewName
        {
            get { return _recalcOnNewName; }
            set { _recalcOnNewName = value;
                recalcForNewName();
            }
        }

        private bool _recalcOnNewName = true;

        public event EventHandler ThemeChanged;

        #endregion

        ///<summary>
        /// Remove the defined name with name 'name'. If no defined name has name, this method does nothing.
        ///</summary>
        ///<param name="name">The name of the defined name to remove</param>
        ///<exception cref="ArgumentException"></exception>
        public void RemoveDefinedName(string name)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentException("name");
            DefinedName n1 = Enumerable.FirstOrDefault(DefinedNames, n => n.Name == name);
            if (n1 != null)
                DefinedNames.Remove(n1);
        }

        internal void AddToReadContext(ReadContext context)
        {
            context.AddData<IThemedElement>(this);
            context.AddData(this);
            context.AddData<IReferenceFactory>(this);
        }

        internal bool HasWorksheetName(string s)
        {
            foreach (var ws in Sheets)
                if (string.Compare(ws.Name, s, true) == 0)
                    return true;
            return false;
        }

        /* private WorkbookResolver _tempReader;

        internal WorkbookResolver TempReader
        {
            get { return _tempReader; }
            set { _tempReader = value; }
        }*/
        /// <summary>
        /// Create a new worksheet and add it to the collection of sheets.
        /// </summary>
        /// <returns>The newly created worksheet</returns>
        public Worksheet AddWorksheet()
        {
            _document.SetDirty();
            var worksheet = new Worksheet(this, new SheetInfo(GetAvailableName(1)));
            worksheet.SetDefaults();
            Sheets.Add(worksheet);
            if (Sheets.Count == 1)
                View.ActiveSheet = worksheet;
            return worksheet;
        }
        /// <summary>
        /// Create a chart sheet and add it to the collection of sheets.
        /// </summary>
        /// <returns></returns>
        public Chartsheet AddChartsheet()
        {
            _document.SetDirty();
            var worksheet = new Chartsheet(this, new SheetInfo(GetAvailableName(1)));
            Sheets.Add(worksheet);
            if (Sheets.Count == 1)
                View.ActiveSheet = worksheet;
            return worksheet;
        }

        internal VbModule AddVbModule()
        {
            _document.SetDirty();
            var worksheet = new VbModule(this, new SheetInfo(GetAvailableName(1)));
            Sheets.Add(worksheet);
            if (Sheets.Count == 1)
                View.ActiveSheet = worksheet;
            return worksheet;
        }

        internal Worksheet InsertWorksheet(int index)
        {
            Contract.Requires(!AreSheetsProtected);
            if (AreSheetsProtected)
                throw new InvalidOperationException();
            _document.SetDirty();
            index = Math.Max(index, 0);
            var worksheet = new Worksheet(this, new SheetInfo(GetAvailableName(index + 1)));
            worksheet.SetDefaults();
            Sheets.Insert(index, worksheet);
            if (Sheets.Count == 1)
                View.ActiveSheet = worksheet;
            return worksheet;
        }

        internal string GetAvailableName(int index, string name)
        {
            int nameIndex = index;
            while (HasWorksheetName(string.Format(name, nameIndex)))
                nameIndex++;
            return string.Format(name, nameIndex);
        }


        internal string GetAvailableName(int index)
        {
            return GetAvailableName(index, StringTable.DefaultSheetName);
        }

        /// <summary>
        /// Returns the worksheet with a given name.
        /// </summary>
        /// <param name="name">The name of the worksheet. This is case insensitive</param>
        /// <returns>The worksheet</returns>
        public Worksheet GetWorksheet(string name)
        {
            if (name == null)
                return null;
            string trimmed = name.Trim();
            if (trimmed.StartsWith("'") || trimmed.EndsWith("'"))
                name = name.Trim();
            if (name.Length > 0 && name[0] == '\'')
                return GetWorksheet(name.Trim('\''));
            foreach (Sheet ws in Sheets)
                if (ws.Name == name)
                    return ws as Worksheet;
            foreach (Sheet ws in Sheets)
                if (string.Compare(ws.Name, name, true) == 0)
                    return ws as Worksheet;
            if (name.StartsWith("["))
            {
                name = name.Substring(1);
                string[] ss = name.Split(']');
                int i;
                if (int.TryParse(ss[0], out i))
                {
                    if (i == 0)
                        return GetWorksheet(ss[1]);
                    ExternalLink link = GetExternalLink(i);
                    return link.ExternalBook.GetWorksheet(ss[1]);
                }
            }
            if (name.Contains("["))
            {
                int firstBraket = name.IndexOf('[');
                int secondBraket = name.IndexOf(']', firstBraket);
                if (secondBraket > 0)
                {
                    string folderName = name.Substring(0, firstBraket);
                    string fileName = name.Substring(firstBraket + 1, secondBraket - firstBraket - 1);
                    string rest = name.Substring(secondBraket + 1, name.Length - secondBraket - 1);
                    ExternalLink link = getExternalLink(folderName + fileName);
                    return link.ExternalBook.GetWorksheet(rest);
                }
            }
            return null;
        }

        ~Workbook()
        {
            if (_selfWeakReference != null)
            {
                _selfWeakReference.Close();
                _selfWeakReference = null;
            }
        }

        private WorkbookWeakReference ensureWeakReference(Theme theme)
        {
            if (_selfWeakReference == null)
                _selfWeakReference = new WorkbookWeakReference(this, theme);
            return _selfWeakReference;
        }

        private void valuePropertyChangedprotected(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "FontScheme")
                foreach (Worksheet worksheet in Sheets)
                {
                    foreach (Cell c in worksheet.NonVolatileCells)
                        c.InvalidateFontFamily();
                }
        }

        /// <summary>
        /// Create a new workbook in a document
        /// </summary>
        /// <param name="document">The containing document</param>
        /// <returns>The newly created workbook</returns>
        public static Workbook Create(SpreadsheetDocument document)
        {
            Contract.Requires(document != null);
            var result = new Workbook(document);

            for (int i = 1; i <= 3; i++)
            {
                result.AddWorksheet();
            }
            result.Document.IsDirty = false;
            return result;
        }

        internal static Workbook CreateEmpty(SpreadsheetDocument document)
        {
            var result = new Workbook(document);
            result.Document.IsDirty = false;
            return result;
        }

        public Worksheet GetActiveWorksheet()
        {
            if (_view.ActiveSheet is Worksheet)
                return (_view.ActiveSheet as Worksheet);
            return null;
        }

        public void RemoveActiveSheet()
        {
            int id = Sheets.IndexOf(_view.ActiveSheet);
            using (CreateUndoScope(StringTable.DeleteSheet))
            {
                this.UndoManager.Add(new SheetRemovedUndoUnit(this, _view.ActiveSheet, id));
                int newId = -1;
                if (id < Sheets.Count - 1)
                    newId = id;
                else
                    newId = id - 1;
                Sheets.Remove(_view.ActiveSheet);
                Worksheet oldWorksheet = _view.ActiveSheet as Worksheet;
                if (newId >= 0)
                    View.ActiveSheet = Sheets[newId];
                if (oldWorksheet != null)
                {
                    oldWorksheet.DeleteCells();
                }
            }
        }


        internal bool IsValidName(string s, Worksheet caller)
        {
            if (string.IsNullOrEmpty(s))
                return false;
            if (BuiltInDefinedNames.IsBuiltInName(s))
                return true;
            foreach (char c in s)
            {
                if (!char.IsLetterOrDigit(c) && c != '_' && c != '.')
                    return false;
            }
            CellReference cr;
            if (caller == null)
            {
                if (A1Nc.TryParseCellReference(s, null, this, out cr))
                    return false;
            }
            else
            {
                if (A1Nc.TryParseWorksheetCellReference(s, null, caller, out cr))
                    return false;
            }
            return true;
        }

        //private readonly List<PivotCache> _pivotCaches = new List<PivotCache>();

        // internal List<PivotCache> PivotCaches
        // {
        //    get { return _pivotCaches; }
        // }

        internal void AddDefinedName(DefinedName definedName)
        {
            if (!IsValidName(definedName.Name, null))
                throw new ArgumentException("definedName");
            using (CreateUndoScope(StringTable.DefineName + " " + definedName.Name))
            {
                var toRemove = new List<DefinedName>();
                foreach (DefinedName n in DefinedNames)
                {
                    if ((n.Name == definedName.Name || (n.Formula == definedName.Formula && definedName.Formula != null)) &&
                        n.LocalSheet == definedName.LocalSheet)
                    {
                        toRemove.Add(n);
                    }
                }
                foreach (DefinedName n in toRemove)
                {
                    DefinedNames.Remove(n);
                }
                DefinedNames.Add(definedName);
                recalcForNewName();
            }
        }

        private void recalcForNewName()
        {
            if (!RecalcOnNewName)
                return;
            if (this.DefinedNames.Count == 0)
                return;
            foreach (Cell c in getNonEmptyCells()) //\\ t
                if (c.Value is Error)
                {
                    c.Recalculate(); //\\
                }
        }

        private IEnumerable<Cell> getNonEmptyCells()
        {
            foreach (Worksheet ws in Enumerable.OfType<Worksheet>(Sheets))
                foreach (Cell c in ws.GetCells())
                    yield return c;
        }

        internal UndoUnitScope CreateUndoScope(string name)
        {
            if (CurrentUndoScope != null)
                return null;
            if (InPreviewMode)
            {
                return new UndoUnitScope(new CompositeUndoUnit(name), this, _previewUndoManager);
            }
            if (CurrentUndoScope != null)
                return null;
            if (!UndoManager.IsEnabled)
                return null;
            return new UndoUnitScope(new CompositeUndoUnit(name), this, UndoManager);
        }

        internal UndoUnitScope CreateRangeUndoScope(Range range, CellProperty property)
        {
            if (CurrentUndoScope != null)
                return null;
            if (InPreviewMode)
            {
                return new UndoUnitScope(new RangeUndoUnit(range, property), this, _previewUndoManager);
            }
            if (!UndoManager.IsEnabled)
                return null;
            return new UndoUnitScope(new RangeUndoUnit(range, property), this, UndoManager);
        }

        /*internal UndoUnitScope CreateUndoScope(SRID id)
        {
            if (InPreviewMode)
            {
                return new UndoUnitScope(new CompositeUndoUnit(id), previewUndoManager);
            }
            if (!this.UndoManager.IsEnabled)
                return null;
            return new UndoUnitScope(new CompositeUndoUnit(id), UndoManager);
        }*/

        internal UndoUnitScope CreateUndoScope(ICompositeUndoUnit u)
        {
            if (CurrentUndoScope != null)
                return null;
            if (u == null)
                return null;
            if (!UndoManager.IsEnabled)
                return null;
            return new UndoUnitScope(u, this, UndoManager);
        }

        internal void AddUndoUnit(IUndoUnit u)
        {
            if (InPreviewMode)
            {
                _previewUndoManager.Add(u);
                return;
            }
            if (!UndoManager.IsEnabled)
                return;
            UndoManager.Add(u);
        }

        ///<summary>
        /// Undo all units until a given unit
        ///</summary>
        ///<param name="undoUnit">The action to undo</param>
        public void Undo(IUndoUnit undoUnit)
        {
            UndoManager.Undo(undoUnit);
        }

        ///<summary>
        /// Undo the last action
        ///</summary>
        public void Undo()
        {
            UndoManager.Undo();
        }

        ///<summary>
        /// Redo all units until a given unit.
        ///</summary>
        ///<param name="undoUnit">The undo unit to redo</param>
        public void Redo(IUndoUnit undoUnit)
        {
            UndoManager.Redo(undoUnit);
        }

        ///<summary>
        /// Redo the last undo unit.
        ///</summary>
        public void Redo()
        {
            UndoManager.Redo();
        }

        internal Range GetRange(string formula)
        {
            RangeReference r = GetRangeReference(formula);
            if (r != null)
                return r.Range;
            return null;
        }

        internal RangeReference GetRangeReference(string formula)
        {
            var parser = new ExpressionParser(this, LocalizationMode.NonLocalized, CellReferenceMode.Dynamic);
            Expression expression = parser.Expression("=" + formula);
            expression = ParenthesizedExpression.UnfoldedParenthesisExpression(expression);
            if (expression is RangeReferenceExpression)
                return ((RangeReferenceExpression) expression).RangeReference;
            if (expression is CellReferenceExpression)
            {
                var e = (CellReferenceExpression) expression;
                return new SingleCellRangeReference(e.CellReference);
            }

            var f = new CompositeFormula(formula);
            if (f.IsError)
                return null;
            DefinedName name = Enumerable.FirstOrDefault(DefinedNames, nn => nn.Name == f.Formula);
            if (name != null)
            {
                return GetRangeReference(name.Formula);
            }
            //   NamingConvention.TryParseRangeReference(formula, this, out result);
            //   NamingConvention.TryParseCellReference(formula, null, this, out cellRef);
            throw new NotImplementedException();
        }


        internal void ReadXml(ReadContext context)
        {
            IOfficeXmlSerializable s = this;
            s.ReadXml(context);
        }

        internal DataConnection GetConnectionById(int i)
        {
            foreach (DataConnection c in Connections)
                if (c.Id == i)
                    return c;
            throw new InvalidOperationException();
        }
        /// <summary>
        /// Define a new name for the workbook
        /// </summary>
        /// <param name="name">The name of the defined name</param>
        /// <param name="formula">The formula of the defined name</param>
        /// <returns>The new defined name</returns>
        public DefinedName DefineName(string name, string formula)
        {
            Contract.Requires(!string.IsNullOrEmpty(name));
            Contract.Requires(!string.IsNullOrEmpty(formula));
            if (string.IsNullOrEmpty(name))
                throw new ArgumentException("name");
            if (string.IsNullOrEmpty(formula))
                throw new ArgumentException("formula");
            var n = new DefinedName(this) { Name = name, Formula = formula };
            DefinedNames.Add(n);
            return n;
        }
        /// <summary>
        /// Define a name for a range
        /// </summary>
        /// <param name="name">The name of the defined name</param>
        /// <param name="range">The range of the defined name</param>
        /// <returns>The newly created defined name</returns>
        public DefinedName DefineName(string name, Range range)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentException("name");
            if (range == null)
                throw new ArgumentNullException("range");
            if (range.Worksheet.Workbook != this)
                throw new ArgumentException("range");
            using (this.CreateUndoScope(StringTable.DefineName + " " + name))
            {
                var n = new DefinedName(this) {Name = name, Formula = range.FullLabel};
                var compute = n.Value;
                DefinedNames.Add(n);
               /* this.AddUndoAction(n,
                    nn => DefinedNames.Remove(nn));
                */return n;
            }
        }
        internal DefinedName DefineName(string name, Expression expression)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentException("name");
            if (expression == null)
                throw new ArgumentException("expression");
            using (this.CreateUndoScope(StringTable.DefineName))
            {
                var n = new DefinedName(this) {Name = name, Expression = expression};
                DefinedNames.Add(n);
                this.AddUndoAction(n, nn => DefinedNames.Remove(nn));
                return n;
            }
        }

        /// <summary>
        /// Remove a defined name
        /// </summary>
        /// <param name="name">The name of the defined name to remove</param>
        public void RemoveDefinendName(string name)
        {
            Contract.Requires(!string.IsNullOrEmpty(name));
            if (string.IsNullOrEmpty(name))
                throw new ArgumentException("name");
            DefinedName n1 = Enumerable.FirstOrDefault(DefinedNames, n => n.Name == name);
            if (n1 != null)
                DefinedNames.Remove(n1);
        }

        internal ExternalLink GetExternalLink(int id)
        {
            Contract.Requires(id > 0);
            Contract.Requires((id - 1) < ExternalLinks.Count);

            return ExternalLinks[id - 1];
        }

        private ExternalLink getExternalLink(string path)
        {
            string s1 = normalizedPath(path);
            ExternalLink result = Enumerable.FirstOrDefault(ExternalLinks, l => l.ExternalBook.Document.Name == s1);
            if (result == null)
                result = Enumerable.FirstOrDefault(ExternalLinks, l => l.ExternalBook.Document.FileName == s1);
            if (result == null)
                result = Enumerable.FirstOrDefault(ExternalLinks, l => normalizedPath(l.ExternalBook.Document.FileName) == s1);
            Contract.Assert(result != null);
            return result;
        }

        private static string normalizedPath(string s)
        {
            string result = Uri.UnescapeDataString(s); //\\ s.Replace("%20", " ");
            if (!result.StartsWith("\\") && !s.StartsWith("/") && !s.Contains(":"))
                result = "/" + result;
            try
            {
                result = Path.GetFullPath(result);
            }
            catch (NotSupportedException)
            {
            }
            return result;
        }
        /// <summary>
        /// Calculate all the worksheets in the workbook
        /// </summary>
        public void Calculate()
        {
            foreach (var sheet in Enumerable.OfType<Worksheet>(Sheets))
                sheet.Calculate();
        }


        internal TableStyle GetStyle(string name)
        {
            foreach (TableStyle style in StyleSheet.TableStyles)
            {
                if (style.Name == name)
                    return style;
            }
            return TableStyles.GetStyle(name);
        }

        internal void AddUndoAction<T>(T data, Action<T> action)
        {
            if (!UndoEnabled)
                return;
            UndoManager.Add(new GenericUndoUnit<T>(data, action));
        }

        #region Nested type: UndoManagerDisabler

        private class UndoManagerDisabler : IDisposable
        {
            private readonly UndoManager _manager;
            private readonly bool _oldIsEnabled;

            public UndoManagerDisabler(UndoManager manager)
            {
                _manager = manager;
                _oldIsEnabled = manager.IsEnabled;
                manager.IsEnabled = false;
            }

            #region IDisposable Members

            public void Dispose()
            {
                _manager.IsEnabled = _oldIsEnabled;
            }

            #endregion
        }
        internal IDisposable CreateUndoManagerDisabler()
        {
            return new UndoManagerDisabler(this.UndoManager);
        }
        #endregion

        #region Nested type: WorkbookWeakReference

        private class WorkbookWeakReference
        {
            private readonly Theme _theme;
            private readonly WeakReference _workbookReference;

            public WorkbookWeakReference(Workbook wb, Theme theme)
            {
                _workbookReference = new WeakReference(wb);
                _theme = theme;
            }

            internal void ValuePropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (_workbookReference.IsAlive)
                {
                    var workbook = (Workbook) _workbookReference.Target;
                    workbook.valuePropertyChangedprotected(sender, e);
                }
            }

            internal void Close()
            {
                _theme.PropertyChanged -= ValuePropertyChanged;
            }
        }

        #endregion

        internal void IncrementRowColumnGeneration()
        {
            RowColumnGeneration = RowColumnGeneration.Increment();
        }
        private static readonly EventArgs _dummyEventArgs = new EventArgs();

        internal void NotifyValueChanged(Cell cell, object oldValue, object newValue)
        {
            var handler = CellValueChanged;
            if (handler != null)
                handler(cell, new CellValueChangedEventArgs<object>(cell, oldValue, newValue));
        }
        internal void NotifyPreviewValueChanged(Cell cell)
        {
            var handler = PreviewCellValueChanged;
            if (handler != null)
                handler(cell, new CellEventArgs(cell));
        }
        internal void NotifyPreviewFormulaChanged(Cell cell, Expression oldExpression, Expression newExpression)
        {
            var handler = PreviewCellFormulaChanged;
            if (handler != null)
                handler(cell, new CellFormulaChangedEventArgs(cell, oldExpression, newExpression));
        }
        internal void NotifyFormulaChanged(Cell cell, Expression oldExpression, Expression newExpression)
        {
            var handler = CellFormulaChanged;
            if (handler != null)
                handler(cell, new CellFormulaChangedEventArgs(cell, oldExpression, newExpression));
        }

        internal void NotifyBigDataTableAdded(Table newTable, string strTableAdded)
        {
            var handler = TableAdded;
            if (handler != null)
                handler(newTable, new TableEventArgs(newTable, this, strTableAdded));
        }

       

        /// <summary>
        /// Occurs when the value of a cell has changed.
        /// </summary>
        public event EventHandler<CellValueChangedEventArgs<object>> CellValueChanged;

        /// <summary>
        /// Occurs before the value of a cell has changed.
        /// </summary>
        public event EventHandler<CellEventArgs> PreviewCellValueChanged;

        /// <summary>
        /// Occurs when the formula of a cell has changed.
        /// </summary>
        public event EventHandler<CellFormulaChangedEventArgs> CellFormulaChanged;

        /// <summary>
        /// Occurs before the formula of a cell has changed.
        /// </summary>
        public event EventHandler<CellFormulaChangedEventArgs> PreviewCellFormulaChanged;


        /// <summary>
        /// Occurs when a new table is added in the current workboox. Its equivalent tab should be displayed.
        /// </summary>
        public event EventHandler<TableEventArgs> TableAdded;

        /// <summary>
        /// Occurs when a new table is added in the current workboox. Its equivalent tab should be displayed.
        /// </summary>
        public event EventHandler<TableDefinitionLoadedEventArgs> TableDefinitionLoaded;


        public void ResetDefinedNames()
        {
            foreach (var definedName in this.DefinedNames)
                definedName.Reset();
        }


        public void DisplayDataTables()
        {
            /*
            foreach (string strTable in _customBigDataTableDefinitions)
            {
                if (TableDefinitionLoaded != null)
                {
                    TableDefinitionLoadedEventArgs evtTableDefinition = new TableDefinitionLoadedEventArgs(strTable);
                    TableDefinitionLoaded(this, evtTableDefinition);
                }
            }
            */
        }
    }
}